package ThreadingSafety;

/**
 *  "懒汉模式"的单例模式
 *   注意: 1. 以下代码包括注释请按顺序往下看.
 *        2. 这里的代码面试可能会考, 要记住, 很重要!!!!
 */
class SingletonLazy {

    // 与饿汉模式不同的是, 在此并没有直接创建 SingletonLazy 类的唯一实例.
    //private static SingletonLazy instance = null;
    private volatile static SingletonLazy instance = null;  //看到后面就直到这行加 volatile 的意义了.
    // 与饿汉模式不同的是, 在此并没有直接创建 SingletonLazy 类的唯一实例.

    public static SingletonLazy getInstance0() {
        if(instance == null) {
             instance = new SingletonLazy();
            // 这里才是真正创建实例的时机, 只有首次调用 getInstance 时,才会触发, 后续在调用 getInstance, 直接返回 instance 实例.
            // 如果后续没有调用getInstance,那么就把创建实例的开销节省下来了,即使调用了,但是调用得比较晚,创建实例得时机也就晚了,就和其他
            // 耗时操作岔开了(一般程序刚启动时,要初始化的东西很多,系统资源紧张),因此懒汉模式效率比饿汉模式高~~
        }
        return instance;
    }

    // 可是以上的 getInstance 的写法存在这一个致命的问题: 当多个线程同时调用 getInstance 时,因为在此不仅涉及了
    // "读",还涉及了"修改"操作,因此存在线程不安全问题!!!
    // 当线程t2正在LOAD时,线程t1还没有修改完,此时t2读到的还是旧的值,此时t2仍然可以进入new操作,
    // 此时就造成了两个线程都创建了实例,这违背了 "单例模式" 的初衷!!!
    // 因此,为了解决该问题,我们需要加锁!!! 就有了以下代码:

    public static SingletonLazy getInstance2() {
        synchronized (SingletonLazy.class) {
            if(instance == null) {
                instance = new SingletonLazy();
            }
            return instance;
        }
    }

    // 这样的写法就解决了刚刚提到的问题,但是又出现了新的问题:
    // 懒汉模式 中,线程不安全并不是一直都不安全,只是在实例真正创建之前会触发线程不安全问题,一旦创建好了,线程就安全了~~
    // 而在上述加锁代码的写法,即使在线程安全的时候,仍在还在加锁,而实际上,加锁的代价还是比较大的(可能会涉及 用户态
    // 到 内核态 的切换,这样的切换成本比较高!!) 因此加锁不能"无脑加", 在该加的时候加,不该加的时候就不加~~
    // 此时为了解决这个问题, 需要在加锁的外层加上一层判定条件! 如下代码:

    public static SingletonLazy getInstance() {
        if(instance == null) { // 判断需不需要加锁
            synchronized (SingletonLazy.class) {
                if(instance == null) {
                    // 判断是否是 整个进程 首次调用 getInstance,是不是创建实例的时机
                    instance = new SingletonLazy();
                }
            }
        }
        // 值得注意的是,这里的两个if判断条件相同只是代码原因,是巧合,并且两个if执行的时间间隔可能"沧海桑田",这是因为
        // 两个if之间隔着一个加锁操作,加锁就可能产生竞争,竞争就会造成阻塞,一旦阻塞,什么时候被唤醒,就没谱了~~
        // 因此, 两个if执行的结果可能也会不同!!
        return instance;
    }

    // 上述的写法还存在一个问题,可能会导致线程不安全问题: 指令重排序!!!
    // new操作本质上也是分成三个步骤: 1.申请内存,得到内存首地址;  2.调用构造方法,来初始化实例;
    //                           3.把内存的首地址赋值给instance引用～~
    // 在单线程的角度下,2和3是可以调换顺序的!!!(单线程的情况下,此时2和3先执行谁,后执行谁效果一样).
    // 假设此处触发指令重排序~~并且按照132的顺序执行的, 有可能t1执行了1和3之后,执行2之前, t2调用了getInstance,
    // 此时这个getInstance就会认为Instance非空, 就直接返回了Instance,t2就得到了不完全的对象,只是有内存,内存上的数据无效~~
    // 并且在后续可能就会针对Instance进行解引用操作(使用里面的属性/方法).
    // 这就就是指令重排序带来的问题.要想解决这个问题,办法就是, 禁止指令重排序~~ 就需要用到 volatile 关键字!!!
    // 将 private static SingletonLazy instance = null; 写为 private volatile static SingletonLazy instance = null;

    // 所以 volatile 关键字 可以解决 内存可见性问题 和 指令重排序问题!!!
}


public class TestDemo19 {
    public static void main(String[] args) {
        // 在类外获取实例.
        SingletonLazy instance = SingletonLazy.getInstance();
    }
}

// TestDemo 20 讲解 阻塞队列